#include "MachineCode.h"
#include "Type.h"
extern FILE* yyout;

MachineOperand::MachineOperand(int tp, int val)
{
    this->type = tp;
    if(tp == MachineOperand::IMM)
        this->val = val;
    else 
        this->reg_no = val;
}

MachineOperand::MachineOperand(std::string label)
{
    this->type = MachineOperand::LABEL;
    this->label = label;
}

bool MachineOperand::operator==(const MachineOperand&a) const
{
    if (this->type != a.type)
        return false;
    if (this->type == IMM)
        return this->val == a.val;
    return this->reg_no == a.reg_no;
}

bool MachineOperand::operator<(const MachineOperand&a) const
{
    if(this->type == a.type)
    {
        if(this->type == IMM)
            return this->val < a.val;
        return this->reg_no < a.reg_no;
    }
    return this->type < a.type;

    if (this->type != a.type)
        return false;
    if (this->type == IMM)
        return this->val == a.val;
    return this->reg_no == a.reg_no;
}

void MachineOperand::PrintReg()
{
    switch (reg_no)
    {
    case 11:
        fprintf(yyout, "fp");
        fprintf(stderr, "fp");
        break;
    case 13:
        fprintf(yyout, "sp");
        fprintf(stderr, "sp");
        break;
    case 14:
        fprintf(yyout, "lr");
        fprintf(stderr, "lr");
        break;
    case 15:
        fprintf(yyout, "pc");
        fprintf(stderr, "pc");
        break;
    default:
        fprintf(yyout, "r%d", reg_no);
        fprintf(stderr, "r%d", reg_no);
        break;
    }
}

void MachineOperand::output() 
{
    /* HINT：print operand
    * Example:
    * immediate num 1 -> print #1;
    * register 1 -> print r1;
    * lable addr_a -> print addr_a; */
    switch (this->type)
    {
    case IMM:
        fprintf(yyout, "#%d", this->val);
        fprintf(stderr, "#%d", this->val);
        break;
    case VREG:
        fprintf(yyout, "v%d", this->reg_no);
        fprintf(stderr, "v%d", this->reg_no);
        break;
    case REG:
        PrintReg();
        break;
    case LABEL:
        if (this->label.substr(0, 2) == ".L"){
            fprintf(yyout, "%s", this->label.c_str());
            fprintf(stderr, "%s", this->label.c_str());
        }
        else if (this->label.substr(0, 1) == "@"){
            fprintf(yyout, "%s", this->label.c_str() + 1);
        }
        else{
            fprintf(yyout, "addr_%s%d", this->label.c_str(),parent->getParent()->getParent()->getParent()->global_count);
            fprintf(stderr, "addr_%s%d", this->label.c_str(),parent->getParent()->getParent()->getParent()->global_count);
        }
    default:
        break;
    }
}

void MachineInstruction::PrintCond()
{
    // TODO
    switch (cond)
    {
    case LT:
        fprintf(yyout, "lt");
        fprintf(stderr, "lt");
        break;
    case LE:
        fprintf(yyout, "le");
        fprintf(stderr, "le");
        break;
    case GT:
        fprintf(yyout, "gt");
        fprintf(stderr, "gt");
        break;
    case GE:
        fprintf(yyout, "ge");
        fprintf(stderr, "ge");
        break;
    case EQ:
        fprintf(yyout, "eq");
        fprintf(stderr, "eq");
        break;
    case NE:
        fprintf(yyout, "ne");
        fprintf(stderr, "ne");
        break;
    default:
        break;
    }
}
void MachineInstruction::insertBefore(MachineInstruction* inst) {
    auto& instructions = parent->getInsts();
    auto it = std::find(instructions.begin(), instructions.end(), this);
    instructions.insert(it, inst);
}

void MachineInstruction::insertAfter(MachineInstruction* inst) {
    auto& instructions = parent->getInsts();
    auto it = std::find(instructions.begin(), instructions.end(), this);
    instructions.insert(++it, inst);
}
BinaryMInstruction::BinaryMInstruction(
    MachineBlock* p, int op, 
    MachineOperand* dst, MachineOperand* src1, MachineOperand* src2, 
    int cond)
{
    this->parent = p;
    this->type = MachineInstruction::BINARY;
    this->op = op;
    this->cond = cond;
    this->def_list.push_back(dst);
    this->use_list.push_back(src1);
    this->use_list.push_back(src2);
    dst->setParent(this);
    src1->setParent(this);
    src2->setParent(this);
}

void BinaryMInstruction::output() 
{
    // TODO: 
    // Complete other instructions
    switch (this->op)
    {
    case BinaryMInstruction::ADD:
        fprintf(yyout, "\tadd ");
        fprintf(stderr, "\tadd ");
        this->PrintCond();
        this->def_list[0]->output();
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[0]->output();
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[1]->output();
        fprintf(yyout, "\n");
        fprintf(stderr, "\n");
        break;
    case BinaryMInstruction::SUB:
        fprintf(yyout, "\tsub ");
        fprintf(stderr, "\tsub ");
        this->PrintCond();
        this->def_list[0]->output();
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[0]->output();
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[1]->output();
        fprintf(yyout, "\n");
        fprintf(stderr, "\n");
        break;
    case BinaryMInstruction::MUL:
        fprintf(yyout, "\tmul ");
        fprintf(stderr, "\tmul ");
        this->PrintCond();
        this->def_list[0]->output();
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[0]->output();
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[1]->output();
        fprintf(yyout, "\n");
        fprintf(stderr, "\n");
        break;
    case BinaryMInstruction::DIV:
        fprintf(yyout, "\tsdiv ");
        fprintf(stderr, "\tsdiv ");
        this->PrintCond();
        this->def_list[0]->output();
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[0]->output();
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[1]->output();
        fprintf(yyout, "\n");
        fprintf(stderr, "\n");
        break;
    default:
        break;
    }
}

LoadMInstruction::LoadMInstruction(MachineBlock* p,
    MachineOperand* dst, MachineOperand* src1, MachineOperand* src2,
    int cond)
{
    this->parent = p;
    this->type = MachineInstruction::LOAD;
    this->op = -1;
    this->cond = cond;
    this->def_list.push_back(dst);
    this->use_list.push_back(src1);
    if (src2)
        this->use_list.push_back(src2);
    dst->setParent(this);
    src1->setParent(this);
    if (src2)
        src2->setParent(this);
}

void LoadMInstruction::output()
{
    fprintf(yyout, "\tldr ");
    fprintf(stderr, "\tldr ");
    this->def_list[0]->output();
    fprintf(yyout, ", ");
    fprintf(stderr, ", ");

    // Load immediate num, eg: ldr r1, =8
    if(this->use_list[0]->isImm())
    {
        fprintf(yyout, "=%d\n", this->use_list[0]->getVal());
        fprintf(stderr, "=%d\n", this->use_list[0]->getVal());
        return;
    }

    // Load address
    if(this->use_list[0]->isReg()||this->use_list[0]->isVReg()){
        fprintf(yyout, "[");
        fprintf(stderr, "[");
    }

    this->use_list[0]->output();
    if( this->use_list.size() > 1 )
    {
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[1]->output();
    }

    if(this->use_list[0]->isReg()||this->use_list[0]->isVReg()){
        fprintf(yyout, "]");
        fprintf(stderr, "]");
    }
    fprintf(yyout, "\n");
    fprintf(stderr, "\n");
}

StoreMInstruction::StoreMInstruction(MachineBlock* p,
    MachineOperand* src1, MachineOperand* src2, MachineOperand* src3, 
    int cond)
{
    this->parent = p;
    this->type = MachineInstruction::STORE;
    this->op = -1;
    this->cond = cond;
    this->use_list.push_back(src1);
    this->use_list.push_back(src2);
    if (src3)
        this->use_list.push_back(src3);
    src1->setParent(this);
    src2->setParent(this);
    if (src3)
        src3->setParent(this);
}

void StoreMInstruction::output()
{
    // TODO
    fprintf(yyout, "\tstr ");
    fprintf(stderr, "\tstr ");
    this->use_list[0]->output();
    fprintf(yyout, ", ");
    fprintf(stderr, ", ");
    // store address
    if (this->use_list[1]->isReg() || this->use_list[1]->isVReg()){
            fprintf(yyout, "[");
            fprintf(stderr, "[");
    }
    this->use_list[1]->output();
    if (this->use_list.size() > 2) {
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[2]->output();
    }
    if (this->use_list[1]->isReg() || this->use_list[1]->isVReg()){
        fprintf(yyout, "]");
        fprintf(stderr, "]");
    }
    fprintf(yyout, "\n"); 
    fprintf(stderr, "\n");
}

MovMInstruction::MovMInstruction(MachineBlock* p, int op, 
    MachineOperand* dst, MachineOperand* src,
    int cond)
{
    // TODO
    this->parent = p;
    this->type = MachineInstruction::MOV;
    this->op = op;
    this->cond = cond;
    this->def_list.push_back(dst);
    this->use_list.push_back(src);
    dst->setParent(this);
    src->setParent(this);
}

void MovMInstruction::output() 
{
    // TODO
    fprintf(yyout, "\tmov");
    fprintf(stderr, "\tmov");
    PrintCond();
    fprintf(yyout, " ");
    fprintf(stderr, " ");
    this->def_list[0]->output();
    fprintf(yyout, ", ");
    fprintf(stderr, ", ");
    this->use_list[0]->output();
    fprintf(yyout, "\n");
    fprintf(stderr, "\n");
}

BranchMInstruction::BranchMInstruction(MachineBlock* p, int op, 
    MachineOperand* dst, 
    int cond)
{
    // TODO
    this->parent = p;
    this->type = MachineInstruction::BRANCH;
    this->op = op;
    this->cond = cond;
    this->use_list.push_back(dst);
    dst->setParent(this);
}

void BranchMInstruction::output()
{
    // TODO
    switch (op) {
        case B:
            fprintf(yyout, "\tb");
            fprintf(stderr, "\tb");
            PrintCond();
            fprintf(yyout, " ");
            fprintf(stderr, " ");
            this->use_list[0]->output();
            fprintf(yyout, "\n");
            fprintf(stderr, "\n");
            break;
        case BX:
            fprintf(yyout, "\tbx");
            fprintf(stderr, "\tbx");
            PrintCond();
            fprintf(yyout, " ");
            fprintf(stderr, " ");
            this->use_list[0]->output();
            fprintf(yyout, "\n");
            fprintf(stderr, "\n");
            break;
        case BL:
            fprintf(yyout, "\tbl");
            fprintf(stderr, "\tbl");
            PrintCond();
            fprintf(yyout, " ");
            fprintf(stderr, " ");
            this->use_list[0]->output();
            fprintf(yyout, "\n");
            fprintf(stderr, "\n");
            break;
    }
}

CmpMInstruction::CmpMInstruction(MachineBlock* p, 
    MachineOperand* src1, MachineOperand* src2, 
    int cond)
{
    // TODO
    this->parent = p;
    this->type = MachineInstruction::CMP;
    this->op = -1;
    this->cond = cond;
    p->setCmpCond(cond);
    this->use_list.push_back(src1);
    this->use_list.push_back(src2);
    src1->setParent(this);
    src2->setParent(this);
}

void CmpMInstruction::output()
{
    // TODO
    // Jsut for reg alloca test
    // delete it after test
    fprintf(yyout, "\tcmp ");
    fprintf(stderr, "\tcmp ");
    this->use_list[0]->output();
    fprintf(yyout, ", ");
    fprintf(stderr, ", ");
    this->use_list[1]->output();
    fprintf(yyout, "\n");
    fprintf(stderr, "\n");
}

StackMInstrcuton::StackMInstrcuton(MachineBlock* p, int op, 
    std::vector<MachineOperand*> src,
    int cond)
{
    // TODO
    this->parent = p;
    this->type = MachineInstruction::STACK;
    this->op = op;
    this->cond = cond;
    if (!src.empty())
        for (auto it = src.begin(); it != src.end(); it++)
            this->use_list.push_back(*it);
}

void StackMInstrcuton::output()
{
    // TODO
    switch (op) {
        case PUSH:
            fprintf(yyout, "\tpush ");
            fprintf(stderr, "\tpush ");
            break;
        case POP:
            fprintf(yyout, "\tpop ");
            fprintf(stderr, "\tpop ");
            break;
    }
    fprintf(yyout, "{");
    fprintf(stderr, "{");
    this->use_list[0]->output();
    for (long unsigned int i = 1; i < use_list.size(); i++) {
        fprintf(yyout, ", ");
        fprintf(stderr, ", ");
        this->use_list[i]->output();
    }
    fprintf(yyout, "}\n");
    fprintf(stderr, "}\n");

}

MachineFunction::MachineFunction(MachineUnit* p, SymbolEntry* sym_ptr) 
{ 
    this->parent = p; 
    this->sym_ptr = sym_ptr; 
    this->stack_size = 0;
    this->paramsNum =((FunctionType*)(sym_ptr->getType()))->getParamsSe().size();
};

void MachineBlock::output()
{
   fprintf(yyout, ".L%d:\n", this->no);
    for(auto iter : inst_list){
        //we should pop saved regs before return to caller
        if (iter->isBX()) {
                std::vector<MachineOperand* > saved_regs=parent->getSavedRegs();
                auto fp = new MachineOperand(MachineOperand::REG, 11);
                auto lr = new MachineOperand(MachineOperand::REG, 14);
                saved_regs.push_back(fp);
                saved_regs.push_back(lr);
                auto cur_inst = new StackMInstrcuton(this, StackMInstrcuton::POP,saved_regs);
                cur_inst->output();
        }
        iter->output();
    }
        
}
std::vector<MachineOperand*> MachineFunction::getSavedRegs() {
    std::vector<MachineOperand*> regs;
    for (auto it = saved_regs.begin(); it != saved_regs.end(); it++) {
        regs.push_back(new MachineOperand(MachineOperand::REG, *it));
    }
    return regs;
}
void MachineFunction::output()
{
    //std::cout<<"ffffffunction:"<<this->sym_ptr->toStr()<<std::endl;
    const char *func_name = this->sym_ptr->toStr().c_str() + 1;
    fprintf(yyout, "\t.global %s\n", func_name);
    fprintf(yyout, "\t.type %s , %%function\n", func_name);
    fprintf(yyout, "%s:\n", func_name);

    fprintf(stderr, "\t.global %s\n", func_name);
    fprintf(stderr, "\t.type %s , %%function\n", func_name);
    fprintf(stderr, "%s:\n", func_name);
    //fprintf(stdout,"676\n");
    // TODO
    /* Hint:
    *  1. Save fp
    *  2. fp = sp
    *  3. Save callee saved register
    *  4. Allocate stack space for local variable */
    
    // Traverse all the block in block_list to print assembly code.
    auto fp = new MachineOperand(MachineOperand::REG, 11);
    auto sp = new MachineOperand(MachineOperand::REG, 13);
    auto lr = new MachineOperand(MachineOperand::REG, 14);
    std::vector<MachineOperand*> saves=getSavedRegs();
    saves.push_back(fp);
    saves.push_back(lr);
    //printf( "push!!!!!!:\n");
    (new StackMInstrcuton(nullptr, StackMInstrcuton::PUSH, saves))->output();
    (new MovMInstruction(nullptr, MovMInstruction::MOV, fp, sp))->output();
    int off = AllocSpace(0);
    auto size = new MachineOperand(MachineOperand::IMM, off);
    if (off < -255 || off > 255) {
        auto r4 = new MachineOperand(MachineOperand::REG, 4);
        (new LoadMInstruction(nullptr, r4, size))->output();
        (new BinaryMInstruction(nullptr, BinaryMInstruction::SUB, sp, sp, r4))->output();
    } else {
        (new BinaryMInstruction(nullptr, BinaryMInstruction::SUB, sp, sp, size))->output();
    }
    int count = 0;
    for (auto iter : block_list) {
        iter->output();
        /*count += iter->getSize();
        if(count > 160){
            fprintf(yyout, "\tb .F%d\n", parent->getN());
            fprintf(yyout, ".LTORG\n");
            parent->printGlobal();
            fprintf(yyout, ".F%d:\n", parent->getN()-1);
            count = 0;
        }*/
    }
    fprintf(yyout, "\n");
}



void MachineUnit::PrintGlobalDecl() {
    std::vector<int> constIdx;
    if (!global_list.empty())
        fprintf(yyout, "\t.data\n");
        fprintf(stderr, "\t.data\n");
    for (long unsigned int i = 0; i < global_list.size(); i++) {
        IdentifierSymbolEntry* se = (IdentifierSymbolEntry*)global_list[i];
        if (se->getConst()) {
            constIdx.push_back(i);
        } 
        else {
            fprintf(yyout, "\t.global %s\n", se->toStr().c_str());
            fprintf(yyout, "\t.align 4\n");
            fprintf(yyout, "\t.size %s, %d\n", se->toStr().c_str(), se->getType()->getSize() / 8);
            fprintf(yyout, "%s:\n", se->toStr().c_str());
            fprintf(yyout, "\t.word %d\n", se->getValue());

            fprintf(stderr, "\t.global %s\n", se->toStr().c_str());
            fprintf(stderr, "\t.align 4\n");
            fprintf(stderr, "\t.size %s, %d\n", se->toStr().c_str(), se->getType()->getSize() / 8);
            fprintf(stderr, "%s:\n", se->toStr().c_str());
            fprintf(stderr, "\t.word %d\n", se->getValue());
        }
    }
    if (!constIdx.empty()) {
        fprintf(yyout, "\t.section .rodata\n");
        fprintf(stderr, "\t.section .rodata\n");
        for (auto i : constIdx) {
            IdentifierSymbolEntry* se = (IdentifierSymbolEntry*)global_list[i];
            fprintf(yyout, "\t.global %s\n", se->toStr().c_str());
            fprintf(yyout, "\t.align 4\n");
            fprintf(yyout, "\t.size %s, %d\n", se->toStr().c_str(),se->getType()->getSize() / 8);
            fprintf(yyout, "%s:\n", se->toStr().c_str());
            fprintf(yyout, "\t.word %d\n", se->getValue());

            fprintf(stderr, "\t.global %s\n", se->toStr().c_str());
            fprintf(stderr, "\t.align 4\n");
            fprintf(stderr, "\t.size %s, %d\n", se->toStr().c_str(),se->getType()->getSize() / 8);
            fprintf(stderr, "%s:\n", se->toStr().c_str());
            fprintf(stderr, "\t.word %d\n", se->getValue());
        }
    }
}
void MachineUnit::output()
{
    // TODO
    /* Hint:
    * 1. You need to print global variable/const declarition code;
    * 2. Traverse all the function in func_list to print assembly code;
    * 3. Don't forget print bridge label at the end of assembly code!! */
    fprintf(yyout, "\t.arch armv8-a\n");
    fprintf(stderr, "\t.arch armv8-a\n");
    fprintf(yyout, "\t.arch_extension crc\n");
    fprintf(stderr, "\t.arch_extension crc\n");
    fprintf(yyout, "\t.arm\n");
    fprintf(stderr, "\t.arm\n");
    PrintGlobalDecl();
    fprintf(yyout, "\t.text\n");
    fprintf(stderr, "\t.text\n");
    for(auto iter : func_list){
        iter->output();
    }
        
    printGlobal();
}

void MachineUnit::insertGlobal(SymbolEntry* se) {
    global_list.push_back(se);
}

void MachineUnit::printGlobal(){
    for (auto s : global_list) {
        IdentifierSymbolEntry* se = (IdentifierSymbolEntry*)s;
        fprintf(yyout, "addr_%s%d:\n", se->toStr().c_str(), global_count);
        fprintf(stderr,"addr_%s%d:\n", se->toStr().c_str(), global_count);
        fprintf(yyout, "\t.word %s\n", se->toStr().c_str());
        fprintf(stderr, "\t.word %s\n", se->toStr().c_str());
    }
    global_count++;
}
